#include <jni.h>
#include <string>
#include "log.h"

extern "C" {
#include "include/libavcodec/avcodec.h"
#include "include/libavformat/avformat.h"
}

extern "C" JNIEXPORT jstring JNICALL
Java_com_sym_ffmpeg_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject /* this */) {
    std::string hello = "Hello from C++";

    avformat_version();

    return env->NewStringUTF(avcodec_configuration());
}
extern "C"
JNIEXPORT void JNICALL
Java_com_sym_ffmpeg_MainActivity_getFfmpegVersion(JNIEnv *env, jobject thiz) {

    LOGI("ffmpeg version is %s\n", av_version_info());
}
extern "C"
JNIEXPORT void JNICALL
Java_com_sym_ffmpeg_MainActivity_showBuildConfigure(JNIEnv *env, jobject thiz) {
    LOGI("ffmpeg build configure is: \n %s \n", avcodec_configuration());
}
extern "C"
JNIEXPORT void JNICALL
Java_com_sym_ffmpeg_MainActivity_init(JNIEnv *env, jobject thiz) {

}
extern "C"
JNIEXPORT void JNICALL
Java_com_sym_ffmpeg_MainActivity_extractAacFromVideo(JNIEnv *env, jobject thiz, jstring src_path,
                                                     jstring dst_path) {

    //原文件路径
    const char *srcPath = env->GetStringUTFChars(src_path, nullptr);
    //目标文件路径
    const char *dstPath = env->GetStringUTFChars(dst_path, nullptr);
    LOGI("src path is %s\n", srcPath);
    LOGI("dest path is %s\n", dstPath);

    AVFormatContext *in_fmt_ctx = nullptr;
    AVFormatContext *out_fmt_ctx = nullptr;
    AVStream *in_stream = nullptr;
    AVStream *out_stream = nullptr;
    int audio_index;
    AVCodecParameters *in_codecpar = nullptr;
    AVOutputFormat *out_fmt = nullptr;
    AVPacket pkt;

    //in_fmt_ctx
    int ret = avformat_open_input(&in_fmt_ctx, srcPath, nullptr, nullptr);
    if (ret < 0) {
        LOGI("avformat_open_input失败：%s", av_err2str(ret));
        goto end;
    }

    audio_index = av_find_best_stream(in_fmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, nullptr, 0);
    if (audio_index < 0) {
        LOGI("查找音频流失败：%s", av_err2str(audio_index));
        goto end;
    }

    in_stream = in_fmt_ctx->streams[audio_index];
    in_codecpar = in_stream->codecpar;
    if (in_codecpar->codec_type != AVMEDIA_TYPE_AUDIO) {
        LOGI("The Codec type is invalid");
        goto end;
    }

    out_fmt_ctx = avformat_alloc_context();
    out_fmt = av_guess_format(nullptr, dstPath, nullptr);
    out_fmt_ctx->oformat = out_fmt;
    if (!out_fmt) {
        LOGI("Cloud not guess file format");
        goto end;
    }

    out_stream = avformat_new_stream(out_fmt_ctx, nullptr);
    if (!out_stream) {
        LOGI("failed to create out stream");
        goto end;
    }

    //拷贝编解码器参数
    ret = avcodec_parameters_copy(out_stream->codecpar, in_codecpar);
    if (ret < 0) {
        LOGI("avcodec_parameters_copy: %s", av_err2str(ret));
        goto end;
    }
    out_stream->codecpar->codec_tag = 0;

    //创建并初始化目标文件的AVIOContext
    ret = avio_open(&out_fmt_ctx->pb, dstPath, AVIO_FLAG_WRITE);
    if (ret < 0) {
        LOGI("avio_open: %s", av_err2str(ret));
        goto end;
    }

    av_init_packet(&pkt);
    pkt.data = nullptr;
    pkt.size = 0;

    //写文件头
    ret = avformat_write_header(out_fmt_ctx, nullptr);
    if (ret < 0) {
        LOGI("avformat_write_header: %s", av_err2str(ret));
        goto end;
    }

    while (av_read_frame(in_fmt_ctx, &pkt) == 0) {

        if (pkt.stream_index == audio_index) {
            //输入流和输出流的时间基可能不同，因此要根据时间基的不同对时间戳pts进行转换
            pkt.pts = av_rescale_q(pkt.duration, in_stream->time_base, out_stream->time_base);
            pkt.dts = pkt.pts;
            //根据时间基转换
            pkt.duration = av_rescale_q(pkt.duration, in_stream->time_base, out_stream->time_base);
            pkt.pos = -1;
            pkt.stream_index = 0;
            //写入
            av_interleaved_write_frame(out_fmt_ctx, &pkt);
            //释放packet
            av_packet_unref(&pkt);
        }
    }


    //写文件尾部
    av_write_trailer(out_fmt_ctx);


    end:
    if (in_fmt_ctx) { avformat_close_input(&in_fmt_ctx); }
    env->ReleaseStringUTFChars(src_path, srcPath);
    env->ReleaseStringUTFChars(dst_path, dstPath);
    if (out_fmt_ctx) {
        if (out_fmt_ctx->pb) {
            avio_close(out_fmt_ctx->pb);
        }
        avformat_free_context(out_fmt_ctx);
    }
}
extern "C"
JNIEXPORT void JNICALL
Java_com_sym_ffmpeg_MainActivity_extractVideo(JNIEnv *env, jobject thiz, jstring file_path,
                                              jstring dst_file_path) {

    int ret;
    AVFormatContext *in_fmt_ctx = nullptr;
    int video_index;
    AVStream *in_stream = nullptr;
    AVCodecParameters *in_codecpar = nullptr;
    AVFormatContext *out_fmt_ctx = nullptr;
    AVOutputFormat *out_fmt = nullptr;
    AVStream *out_stream = nullptr;
    AVPacket pkt;

    const char *srcPath = env->GetStringUTFChars(file_path, 0);
    const char *dstPath = env->GetStringUTFChars(dst_file_path, 0);

    LOGI("src path is %s", srcPath);
    LOGI("dst path is %s", dstPath);

    ret = avformat_open_input(&in_fmt_ctx, srcPath, nullptr, nullptr);
    if (ret < 0) {
        LOGI("avformat_open_input 失败：%s", av_err2str(ret));
        goto end;
    }

    video_index = av_find_best_stream(in_fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, nullptr, 0);
    if (video_index < 0) {
        LOGI("查找音频流失败：%s", av_err2str(video_index));
        goto end;
    }

    in_stream = in_fmt_ctx->streams[video_index];
    in_codecpar = in_stream->codecpar;
    if (in_codecpar->codec_type != AVMEDIA_TYPE_VIDEO) {
        LOGI("The Codec type is invalid!");
        goto end;
    }

    out_fmt_ctx = avformat_alloc_context();
    out_fmt = av_guess_format(NULL, dstPath, NULL);
    out_fmt_ctx->oformat = out_fmt;
    if (!out_fmt) {
        LOGI("Cloud not guess file format");
        goto end;
    }

    out_stream = avformat_new_stream(out_fmt_ctx, NULL);
    if (!out_stream) {
        LOGI("Failed to create out stream");
        goto end;
    }

    //拷贝编码器参数
    ret = avcodec_parameters_copy(out_stream->codecpar, in_codecpar);
    if (ret < 0) {
        LOGI("avcodec_parameters_copy:%s", av_err2str(ret));
        goto end;
    }
    out_stream->codecpar->codec_tag = 0;

    //创建并初始化目标文件的AVIOContext
    ret = avio_open(&out_fmt_ctx->pb, dstPath, AVIO_FLAG_WRITE);
    if (ret < 0) {
        LOGI("avio_open:%s", av_err2str(ret));
        goto end;
    }
    //initialize packet
    av_init_packet(&pkt);
    pkt.data = nullptr;
    pkt.size = 0;
    //写文件头
    ret = avformat_write_header(out_fmt_ctx, nullptr);
    if (ret < 0) {
        LOGI("avformat_write_header: %s", av_err2str(ret));
        goto end;
    }

    while (av_read_frame(in_fmt_ctx, &pkt) == 0) {

        if (pkt.stream_index == video_index) {

            //输入流和输出流的时间基可能不同，因此要根据时间基的不同对时间戳pts进行转换
            pkt.pts = av_rescale_q(pkt.pts, in_stream->time_base, out_stream->time_base);
            pkt.dts = pkt.pts;

            //根据时间基转换duration
            pkt.duration = av_rescale_q(pkt.duration, in_stream->time_base, out_stream->time_base);
            pkt.pos = -1;
            pkt.stream_index = 0;

            //写入
            av_interleaved_write_frame(out_fmt_ctx, &pkt);

            //释放packet
            av_packet_unref(&pkt);

        }
    }

    //写文件尾部
    av_write_trailer(out_fmt_ctx);


    //释放资源
    end:
    env->ReleaseStringUTFChars(file_path, srcPath);
    env->ReleaseStringUTFChars(dst_file_path, dstPath);
    if (in_fmt_ctx) avformat_close_input(&in_fmt_ctx);
    if (out_fmt_ctx) {
        if (out_fmt_ctx->pb) avio_close(out_fmt_ctx->pb);
        avformat_free_context(out_fmt_ctx);
    }

}